#
# Copyright (C) 2008 ZHENG Zhong <http://www.zhengzhong.net/>
#
# Created on 2008-04-28.
# $Id$
#

import datetime
import logging

from django.template import loader, Context
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, HttpResponseNotFound

from google.appengine.api import users
from google.appengine.api import memcache
from google.appengine.ext import db

from buggarden.hottywood import view_utils
from buggarden.hottywood.models import HotMovie, HotScene, HotUser


#---------------------------------------------------------------------------------------------------

_SIDEBAR_CACHE_KEY = 'hottywood_sidebar'

class Sidebar(object):
    def __init__(self, top_movies, top_users):
        self.top_movies = top_movies
        self.top_users = top_users

def _get_sidebar():
    sidebar = memcache.get(_SIDEBAR_CACHE_KEY)
    if not sidebar:
        top_movies = HotMovie.get_hottest(5)
        top_users = HotUser.get_hottest(5)
        sidebar = Sidebar(top_movies, top_users)
        memcache.set(_SIDEBAR_CACHE_KEY, sidebar, time=3600)  # Keep for one hour.
    return sidebar

#---------------------------------------------------------------------------------------------------

def _add_common_data(ctxt_dict):
    ctxt_dict.update({'sidebar_': _get_sidebar(),})
    return ctxt_dict

#---------------------------------------------------------------------------------------------------

def _as_minutes(str_value):
    minutes = 0
    try:
        str_value = str_value.lower()
        str_value = str_value.replace('hours', ':').replace('hour', ':').replace('h', ':')
        str_value = str_value.replace("'", '').replace(' ', '')
        m_index = str_value.find('m')
        if m_index >= 0:
            str_value = str_value[:m_index]
        values = str_value.split(':')
        if len(values) == 2:
            minutes = int(values[0]) * 60 + int(values[1])
        elif len(values) == 1:
            minutes = int(values[0])
    except Exception, exc:
        minutes = 0
    return minutes

#---------------------------------------------------------------------------------------------------

def not_found(request):
    """Returns the 404 not-found page."""
    
    ctxt_dict = _add_common_data({'url': request.path,})
    ctxt = view_utils.create_context(request, ctxt_dict)
    tmpl = view_utils.get_template('not_found.html')
    return HttpResponseNotFound(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def error(request, message=None):
    # TODO:
    logging.warning('Error occurred: %s' % message)


#---------------------------------------------------------------------------------------------------

def _get_pretty_name(name):
    SPECIAL_PRETTY_NAMES = {
        'faq': 'Frequently asked questions',
    }
    default_pretty_name = ' '.join(name.split('_')).capitalize()
    return SPECIAL_PRETTY_NAMES.get(name, default_pretty_name)

class DocumentPath(object):
    """This class represents a path to a document. Its instance has the following attributes:
    - path: the path to the document (without the ending slash).
    - pretty_name: the pretty name of the document, generated from the last entry of the path.
    - parents: a list of 2-tuple providing a navigation from root to the current document,
               first value is the path, second value is the pretty name.
    """
    
    def __init__(self, path_entries):
        assert len(path_entries) > 0, 'Path entries should not be empty.'
        self.path = '/'.join(path_entries)
        self.pretty_name = _get_pretty_name(path_entries[-1])
        self.parents = []
        for i in range(1, len(path_entries)):
            path = '/'.join(path_entries[0:i])
            pretty_name = _get_pretty_name(path_entries[i - 1])
            self.parents.append((path, pretty_name))

def document(request, path):
    """Returns the document page specified by path."""
    
    try:
        doc_path = DocumentPath(path.split('/'))
        ctxt_dict = _add_common_data({'doc_path': doc_path,})
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('doc/%s.html' % path)
        return HttpResponse(tmpl.render(ctxt))
    except Exception, exc:
        logging.warning('Failed to serve document "%s": %s' % (path, str(exc)))
        return not_found(request)

#---------------------------------------------------------------------------------------------------

def home(request):
    """Returns the home page."""
    
    newly_added_scenes = HotScene.all().order('-submit_date').fetch(5)
    ctxt_dict = _add_common_data({'newly_added_scenes': newly_added_scenes,})
    ctxt = view_utils.create_context(request, ctxt_dict)
    tmpl = view_utils.get_template('home.html')
    return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def add_movie(request):

    # This view is only accessible for signed-in users.
    user = users.get_current_user()
    if not user:
        sign_in_url = users.create_login_url(request.path)
        return HttpResponseRedirect(sign_in_url)
    
    if request.method == 'GET':
    
        # Returns the add movie form.
        prompt = view_utils.Prompt(request.GET)
        ctxt_dict = _add_common_data({'prompt': prompt,})
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('add_movie.html')
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == 'POST':
    
        # Collect user inputs from the add movie form.
        name = request.POST.get('name', '')
        imdb_id = request.POST.get('imdb_id', '')
        run_time = _as_minutes(request.POST.get('run_time', '0'))
        
        # If the movie already exists, redirect to the movie page.
        movie = HotMovie.get_by_imdb_id(imdb_id)
        if movie:
            message = 'Movie with IMDb ID "%s" already exists.' % movie.imdb_id
            params = view_utils.create_info_param(message)
            url = view_utils.create_url('movie/%s/' % movie.key().id(), params)
            return HttpResponseRedirect(url)
        
        try:
            # Create a movie instance and save it to the datastore.
            movie = HotMovie(name=name, imdb_id=imdb_id, run_time=run_time, submitter=user)
            if name and name.strip():
                first_char = name.strip()[0].upper()
                if first_char >= '0' and first_char <= '9':
                    first_char = '0'
                elif first_char >= 'A' and first_char <= 'Z':
                    pass  # Keep it.
                else:
                    first_char = '_'
                movie.abc = [first_char]
            movie.put()
            # Update the hot user profile.
            hot_user = HotUser.get_or_insert('user_%s' % user.user_id(), user=user)
            hot_user.movies_added += 1
            hot_user.put()
            # Redirect.
            message = 'Movie "%s" has been added successfully.' % movie.name
            params = view_utils.create_info_param(message)
            url = view_utils.create_url('movie/%s/' % movie.key().id(), params)
            return HttpResponseRedirect(url)
        
        except Exception, exc:
            # Redirect to the add movie page with an error message.
            message = 'Failed to add movie: %s' % str(exc)
            logging.error(message)
            logging.exception(exc)
            prompt = view_utils.Prompt.create_error(message)
            ctxt_dict = _add_common_data({'prompt': prompt,})
            ctxt = view_utils.create_context(request, ctxt_dict)
            tmpl = view_utils.get_template('add_movie.html')
            return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def _get_alphabet():
    alphabet = [('#', '0'),]
    for c in 'abcdefghijklmnopqrstuvwxyz':
        alphabet.append( (c.upper(), c) )
    alphabet.append( ('?', '_') )
    return alphabet

#---------------------------------------------------------------------------------------------------

def hottest_movies(request):
    MOVIES_PER_PAGE = 10
    if request.method == 'GET':
        # Fetch movies by hot_seconds and the (optional) 'next' offset.
        query = HotMovie.all().order('-hot_seconds').filter('hot_seconds >', 0)
        next = request.GET.get('next')
        if next and next.isdigit() and int(next) < 100:
            offset = int(next)
        else:
            offset = 0
        movies = query.fetch(MOVIES_PER_PAGE + 1, offset)
        # Get the next offset, and refine the movie list.
        if movies and len(movies) > MOVIES_PER_PAGE:
            next = offset + MOVIES_PER_PAGE
            if next >= 100:
                next = None
            movies = movies[0:-1]
        else:
            next = None
        # Return the response.
        ctxt_dict = _add_common_data( {
            'pretty_abc': 'Hottest',
            'alphabet': _get_alphabet(),
            'movies': movies,
            'next': next,
        } )
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('movies.html')
        return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def movies(request, abc=None):
    MOVIES_PER_PAGE = 10
    if request.method == 'GET':
        # Fetch movies by abc and the (optional) 'next' movie ID.
        query = HotMovie.all().order('__key__')
        if len(abc) == 1:
            query.filter('abc =', abc.upper())
        next = request.GET.get('next')
        if next and next.isdigit():
            query.filter('__key__ >=', db.Key.from_path('HotMovie', int(next)))
        movies = query.fetch(MOVIES_PER_PAGE + 1)
        # Get the next movie ID, and refine the movie list.
        if movies and len(movies) > MOVIES_PER_PAGE:
            next = movies[-1].key().id()
            movies = movies[0:-1]
        else:
            next = None
        # Convert abc to a pretty format.
        if abc == 'all':
            pretty_abc = 'All'
        elif abc == '0':
            pretty_abc = '# (0~9)'
        elif abc == '_':
            pretty_abc = 'Non-English alphabet'
        else:
            pretty_abc = abc.upper()
        # Return the response.
        ctxt_dict = _add_common_data( {
            'pretty_abc': pretty_abc,
            'alphabet': _get_alphabet(),
            'movies': movies,
            'next': next,
        } )
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('movies.html')
        return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def movie(request, id):

    movie = HotMovie.get_by_id(int(id))
    if not movie:
        # TODO:
        message = 'The movie #%s cannot be found.' % id
        return error(request, message)
    
    # Note: at this moment, scenes are not yet evaluated.
    scenes = HotScene.all()
    scenes.ancestor(movie).order('start_time')
    
    if request.method == 'GET':
    
        prompt = view_utils.Prompt(request.GET)
        ctxt_dict = _add_common_data({'movie': movie, 'scenes': scenes, 'prompt': prompt,})
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('movie.html')
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == 'POST':
    
        user = users.get_current_user()
        if not user:
            sign_in_url = users.create_login_url(request.path)
            return HttpResponseRedirect(sign_in_url)
        hot_user = HotUser.get_or_insert('user_%s' % user.user_id(), user=user)
        
        AJAX_BOXES = ('overview', 'scenes',)
        ajax_box = request.POST.get('ajax_box')
        
        if ajax_box in AJAX_BOXES:
        
            try:
            
                if ajax_box == 'scenes':
                
                    action = request.POST.get('action', '').strip().lower()
                    
                    if action == 'add_scene':
                        # Create a new scene and add it to the datastore.
                        scene = HotScene(
                            parent=movie,
                            start_time=_as_minutes(request.POST.get('start_time')),
                            seconds=int(request.POST.get('seconds')),
                            description=request.POST.get('description'),
                            actress=request.POST.get('actress'),
                            actor=request.POST.get('actor'),
                            breast=( request.POST.get('breast', '').strip().lower() == 'true' ),
                            hips=( request.POST.get('hips', '').strip().lower() == 'true' ),
                            legs=( request.POST.get('legs', '').strip().lower() == 'true' ),
                            confirmed=[user],
                            submitter=user
                        )
                        video_url = request.POST.get('video_url', '').strip()
                        if video_url.startswith('http://') and len(video_url) > 10:
                            scene.video_url = video_url
                        scene.put()
                        # Update the hot user profile.
                        hot_user.scenes_added += 1
                        hot_user.scenes_voted += 1
                        hot_user.put()
                    
                    elif action == 'vote_scene':
                    
                        # Collect user inputs from the vote scene form.
                        scene_id = int(request.POST.get('scene_id'))
                        vote = request.POST.get('vote', '').strip().lower()
                        # Get the scene by ID.
                        scene = HotScene.get_by_id(scene_id, movie)
                        if not scene:
                            raise Exception('Failed to find scene by ID %s.' % scene_id)
                        # Remove the current user's vote from the scene's existing voter lists.
                        for voter_list in [scene.confirmed, scene.disproved]:
                            if user in voter_list:
                                voter_list.remove(user)
                                hot_user.scenes_voted -= 1
                        # Add the current user's vote to the scene's appropriate voter list.
                        if vote == 'confirmed':
                            scene.confirmed.append(user)
                            hot_user.scenes_voted += 1
                        elif vote == 'disproved':
                            scene.disproved.append(user)
                            hot_user.scenes_voted += 1
                        # Update the scene and the hot user profile in the datastore.
                        scene.put()
                        hot_user.put()
                    
                    else:
                        raise Exception('Unknown action on %s: %s.' % (ajax_box, action))
                
                elif ajax_box == 'overview':
                
                    # Set movie information.
                    movie.name = request.POST.get('name')
                    movie.imdb_id = request.POST.get('imdb_id')
                    movie.run_time = _as_minutes(request.POST.get('run_time', '0'))
                    movie.description = request.POST.get('description')
                    # Set movie's alphabet index.
                    abc = [c.strip().upper() for c in request.POST.get('abc', '').split(',')]
                    movie.abc = filter(lambda x: len(x) == 1, abc)
                    # Update movie in datastore.
                    movie.put()
                
                else:
                    raise Exception('Unknown AJAX box %s.' % ajax_box)
                
                ajax_error = None
            
            except Exception, exc:
                ajax_error = 'Failed to perform action on %s: %s' % (ajax_box, str(exc))
                logging.error(ajax_error)
                logging.exception(exc)
            
            # Return an HTML snippet according to the 'ajax_box'.
            ctxt_dict = _add_common_data( {
                'movie': movie,
                'scenes': scenes,
                'ajax_error': ajax_error,
            } )
            ctxt = view_utils.create_context(request, ctxt_dict)
            tmpl = view_utils.get_template('ajax/movie_%s.html' % ajax_box)
            return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def movie_poster(request, id):

    movie = HotMovie.get_by_id(int(id))
    if not movie:
        message = 'The movie #%s cannot be found.' % id
        logging.error(message)
        return HttpResponseNotFound(message)
    
    if request.method == 'GET':
        if not movie.has_poster():
            message = 'The movie #%s does not have a poster.' % id
            logging.error(message)
            return HttpResponseNotFound(message)
        else:
            return HttpResponse(movie.poster_data, mimetype=movie.poster_type)
    
    elif request.method == 'POST':
        try:
            poster_file = request.FILES.get('poster')
            if poster_file:
                poster_file.open()
                try:
                    movie.poster_type = poster_file.content_type
                    movie.poster_data = poster_file.read()
                finally:
                    poster_file.close()
            elif request.POST.get('delete') == 'true':
                movie.poster_type = None
                movie.poster_data = None
            movie.put()
        except Exception, exc:
            message = 'Failed to update movie poster: %s' % str(exc)
            logging.error(message)
        url = view_utils.create_url('movie/%d/' % movie.key().id())
        return HttpResponseRedirect(url)

#---------------------------------------------------------------------------------------------------

def scene(request, movie_id, scene_id):

    # Get the movie by ID.
    movie = HotMovie.get_by_id(int(movie_id))
    if not movie:
        # TODO:
        message = 'The movie #%s cannot be found.' % id
        return error(request, message)
    # Get the scene by ID.
    scene = HotScene.get_by_id(int(scene_id), movie)
    if not scene:
        # TODO:
        message = 'The scene #%s in movie #%s cannot be found.' % (scene_id, movie_id)
        return error(request, message)
    # Get comments on the scene.
    comments = []  # TODO
    
    if request.method == 'GET':
    
        prompt = view_utils.Prompt(request.GET)
        ctxt_dict = _add_common_data({'movie': movie, 'scene': scene, 'comments': comments,})
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('scene.html')
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == 'POST':
    
        user = users.get_current_user()
        if not user:
            sign_in_url = users.create_login_url(request.path)
            return HttpResponseRedirect(sign_in_url)
        hot_user = HotUser.get_or_insert('user_%s' % user.user_id(), user=user)
        
        AJAX_BOXES = ('overview', 'comments',)
        ajax_box = request.POST.get('ajax_box')
        
        if ajax_box in AJAX_BOXES:
        
            try:
                if ajax_box == 'overview':
                    # Set scene information.
                    scene.description = request.POST.get('description')
                    scene.actress = request.POST.get('actress')
                    scene.actor = request.POST.get('actor')
                    scene.breast = (request.POST.get('breast', '').strip().lower() == 'true')
                    scene.hips = (request.POST.get('hips', '').strip().lower() == 'true')
                    scene.legs = (request.POST.get('legs', '').strip().lower() == 'true')
                    # Set scene's video URL.
                    video_url = request.POST.get('video_url', '').strip()
                    if video_url.startswith('http://') and len(video_url) > 10:
                        scene.video_url = video_url
                    else:
                        scene.video_url = None
                    # Update scene in datastore.
                    scene.put()
                
                elif ajax_box == 'comments':
                    pass
                
                else:
                    raise Exception('Unknown AJAX box %s.' % ajax_box)
                
                ajax_error = None
            
            except Exception, exc:
                ajax_error = 'Failed to perform action on %s: %s' % (ajax_box, str(exc))
                logging.error(ajax_error)
                logging.exception(exc)
            
            # Return an HTML snippet according to the 'ajax_box'.
            ctxt_dict = _add_common_data( {
                'movie': movie,
                'scene': scene,
                'comments': comments,
                'ajax_error': ajax_error,
            } )
            ctxt = view_utils.create_context(request, ctxt_dict)
            tmpl = view_utils.get_template('ajax/scene_%s.html' % ajax_box)
            return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def actor(request):
    pass

#---------------------------------------------------------------------------------------------------

def user_profile(request, id):

    hot_user = HotUser.get_by_key_name('user_%s' % id)
    if not hot_user:
        # TODO:
        message = 'The user #%s cannot be found.' % id
        return error(request, message)
    
    if request.method == 'GET':
    
        # Fetch movies added by this user.
        query = HotMovie.all().filter('submitter =', hot_user.user).order('-submit_date')
        movies_added = [movie for movie in query.fetch(5)]
        # Fetch scenes added by this user.
        query = HotScene.all().filter('submitter =', hot_user.user).order('-submit_date')
        scenes_added = [scene for scene in query.fetch(5)]
        # Fetch scenes confirmed by this user.
        query = HotScene.all().filter('confirmed =', hot_user.user).order('-submit_date')
        scenes_confirmed = [scene for scene in query.fetch(5)]
        # Fetch scenes disproved by this user.
        query = HotScene.all().filter('disproved =', hot_user.user).order('-submit_date')
        scenes_disproved = [scene for scene in query.fetch(5)]
        # Return the result.
        ctxt_dict = _add_common_data({
            'hot_user': hot_user,
            'movies_added': movies_added,
            'scenes_added': scenes_added,
            'scenes_confirmed': scenes_confirmed,
            'scenes_disproved': scenes_disproved,
        })
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('user.html')
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == 'POST':
    
        AJAX_BOXES = ('profile',)
        ajax_box = request.POST.get('ajax_box')
        if ajax_box in AJAX_BOXES:
            try:
                if ajax_box == 'profile':
                    hot_user.full_name = request.POST.get('full_name')
                    hot_user.sex = request.POST.get('sex')
                    hot_user.bio = request.POST.get('bio')
                    hot_user.put()
                else:
                    raise Exception('Unknown AJAX box %s.' % ajax_box)
                ajax_error = None
            except Exception, exc:
                ajax_error = 'Failed to perform action on %s: %s' % (ajax_box, str(exc))
                logging.error(ajax_error)
                logging.exception(exc)
            
            # Return an HTML snippet according to the 'ajax_box'.
            ctxt_dict = _add_common_data({'hot_user': hot_user,})
            ctxt = view_utils.create_context(request, ctxt_dict)
            tmpl = view_utils.get_template('ajax/user_%s.html' % ajax_box)
            return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def user_photo(request, id):

    hot_user = HotUser.get_by_key_name('user_%s' % id)
    if not hot_user:
        message = 'The user #%s cannot be found.' % id
        logging.error(message)
        return HttpResponseNotFound(message)
    
    if request.method == 'GET':
        if not hot_user.has_photo():
            message = 'The user #%s does not have a photo.' % id
            logging.error(message)
            return HttpResponseNotFound(message)
        else:
            return HttpResponse(hot_user.photo_data, mimetype=hot_user.photo_type)
    
    elif request.method == 'POST':
        try:
            photo_file = request.FILES.get('photo')
            if photo_file:
                photo_file.open()
                try:
                    hot_user.photo_type = photo_file.content_type
                    hot_user.photo_data = photo_file.read()
                finally:
                    photo_file.close()
            elif request.POST.get('delete') == 'true':
                hot_user.photo_type = None
                hot_user.photo_data = None
            hot_user.put()
        except Exception, exc:
            message = 'Failed to update user photo: %s' % str(exc)
            logging.error(message)
        url = view_utils.create_url('user/%s/' % hot_user.user_id())
        return HttpResponseRedirect(url)

#---------------------------------------------------------------------------------------------------

def my_profile(request):
    user = users.get_current_user()
    if not user:
        url = view_utils.create_url('')
        return HttpResponseRedirect(url)
    hot_user = HotUser.get_by_key_name('user_%s' % user.user_id())
    if not hot_user:
        ctxt_dict = _add_common_data({})
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('user_no_profile.html')
        return HttpResponse(tmpl.render(ctxt))
    else:
        url = view_utils.create_url('user/%s/' % hot_user.user_id())
        return HttpResponseRedirect(url)

#---------------------------------------------------------------------------------------------------

def cron_update_hottest(request):
    """X-AppEngine-Cron: true
    """
    
    if request.method == 'GET':
    
        cron_logs = []
        
        timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        message = 'Executing cron task "cron_update_hottest" at %s ...' % timestamp
        logging.info(message)
        cron_logs.append(message)
        
        deleted = memcache.delete(_SIDEBAR_CACHE_KEY)
        if not deleted:
            message = 'Failed to delete cached item "%s" from cache.' % _SIDEBAR_CACHE_KEY
            logging.warning(message)
            cron_logs.append(message)
        else:
            message = 'Deleted cached item "%s" from cache successfully.' % _SIDEBAR_CACHE_KEY
            logging.info(message)
            cron_logs.append(message)
        
        ctxt_dict = {'cron_task': 'cron_update_hottest', 'cron_logs': cron_logs,}
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('cron_logs.html')
        return HttpResponse(tmpl.render(ctxt))



    




















